package edu.caece.langprocessor.syntax;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Stack;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;

import edu.caece.langprocessor.lexical.LexicalProcessor;
import edu.caece.langprocessor.syntax.parse.Action;
import edu.caece.langprocessor.syntax.parse.ActionResult;
import edu.caece.langprocessor.syntax.parse.ActionType;
import edu.caece.langprocessor.syntax.parse.GotoActionsTable;
import edu.caece.langprocessor.syntax.parse.ParseStackItem;
import edu.caece.langprocessor.syntax.parse.ShiftReduceActionsTable;
import edu.caece.langprocessor.syntax.tree.Tree;

public class SyntaxProcessor {

	private static final Log LOGGER = LogFactory.getLog(SyntaxProcessor.class);
	private static final String SPLIT_STR = "~";

	private LexicalProcessor lexicalProcessor;
	private Stack<ParseStackItem> parseStack;
	private Map<Integer, ProductionFactory> productionsMap;
	private ShiftReduceActionsTable shiftReduceActionsTable;
	private GotoActionsTable gotoActionsTable;
	private Set<String> vocabulary;
	private Tree parseTree;
	Integer errorCount;
	List<Terminal> errorsList;

	@Autowired
	public SyntaxProcessor(LexicalProcessor lexicalProcesor, ShiftReduceActionsTable shifReduceActionsTable, 
			GotoActionsTable gotoActionsTable, 	Set<String> vocabulary) {
		this.lexicalProcessor = lexicalProcesor;
		this.shiftReduceActionsTable = shifReduceActionsTable;
		this.gotoActionsTable = gotoActionsTable;
		this.vocabulary = vocabulary;
		this.productionsMap = new HashMap<Integer, ProductionFactory>();
		this.parseStack = new Stack<ParseStackItem>();
		this.parseTree = new Tree();
		this.errorCount = 0;
		this.errorsList = new ArrayList<Terminal>();
	}

	public Tree getParseTree(){
		return this.parseTree;
	}
	
	public boolean run() {
		Integer state;
		Action action = null;
		ActionResult actionResult; 
		Terminal terminal;
		Boolean result;
		this.errorCount = 0;
		this.errorsList = new ArrayList<Terminal>();

		// colocamos el estado inicial en el stack
		this.parseStack.push(new ParseStackItem(null, 0));

		terminal = new Terminal(this.lexicalProcessor.getNextLexicalToken());
		do {
			action = null;
			if (terminal.getLexicalToken() != null) {
				logParseStats(terminal, this.parseStack);

				state = this.parseStack.peek().getState();
				action = this.shiftReduceActionsTable
						.getAction(state, terminal);
				actionResult = action.run(this.lexicalProcessor,
						this.productionsMap, this.gotoActionsTable,
						this.parseStack, terminal, parseTree);
				this.errorCount = this.errorCount + actionResult.getErrorCount();
				this.errorsList.addAll(actionResult.getErrorList());
				terminal = actionResult.getTerminalItem(); 
			}
		} while (terminal != null && terminal.getLexicalToken() != null
				&& (action == null || !action.getType().equals(ActionType.accept)));

		result = (action != null && !action.getType().equals(ActionType.accept) && this.errorCount == 0);

		return result;
	}

	public Integer getErrorCount() {
		return errorCount;
	}

	public List<Terminal> getErrorsList() {
		return errorsList;
	}

	private void logParseStats(Terminal terminal, Stack<ParseStackItem> parseStack ) {
		if (LOGGER.isDebugEnabled()) {
			LOGGER.debug("Stack: " + parseStack.toString());
			LOGGER.debug("Token: "
					+ terminal.getLexicalToken().getToken()
					+ " Lexema: "
					+ terminal.getLexicalToken().getLexema()
					+ " Line: " + terminal.getLexicalToken().getLine()
					+ " Column: "
					+ terminal.getLexicalToken().getColumn());
		}
	}


	public LexicalProcessor getLexicalProcessor() {
		return lexicalProcessor;
	}

	public void setProductionsFromStringsList(List<String> list) {
		final Integer productionClass = 0;
		final Integer prodNumber = 1;
		final Integer leftSide = 2;
		String[] productionArray;

		for (String item : list) {

			ArrayList<String> rightSide = null;
			ProductionFactory prod = null;
			
			productionArray = item.split(SPLIT_STR); 

			//tomamos el tipo de nodo
			String pt = productionArray[productionClass];
			// tomamos numero de produccion
			Integer number = Integer.parseInt(productionArray[prodNumber]);
			// luego tomamos la parte izquierda
			String ls = productionArray[leftSide];
			// Luego el resto
			rightSide = new ArrayList<String>();
			for (int i = leftSide+1; i < productionArray.length; i++) {
				rightSide.add(productionArray[i]);
			}
			prod = new ProductionFactory(pt,ls, rightSide, this.vocabulary);
			// colocamos el factory en el mapa de las producciones
			this.productionsMap.put(number, prod);
		}
	}
}
